using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace pong1
{
    /// <summary>
    /// PongGame contains all the game components
    /// </summary>

    public class PongGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont spriteFont;

        // Pong game components
        EventCollection eventCollection;        // Stores events to be executed by different components
        PongPaddle paddlePlayer1;               // Two paddles and a ball
        PongPaddle paddlePlayer2;
        PongBall ball;
        CollisionDetector collisionDetector;    // Detects collisions between game objects
        Audio audio;                            // Plays sounds
        PongScore score;                        // Stores and displays scores
        Physics physics;                        // Performs calculations for deciding velocities of colliding bodies
        UserInput ui;                           // Wraps input layer. Gets input from user. Displays menus.
        float fFrameRateAccumulator;            // Accumulator variable used to manage the frame rate of the game
        Texture2D backgroundTexture2D;
        
        /// <summary>
        /// Creates all the PongGame components. 
        /// Adds functions to delegate functions.
        /// </summary>
        public PongGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            Resources.loadValuesFromFile();     // Load all the configuration variables from file

            IsFixedTimeStep = Resources.IS_FIXED_TIME_STEP;
            fFrameRateAccumulator = 0;

            // Create new eventCollection and add to Services
            eventCollection = new EventCollection();
            Services.AddService(typeof(EventCollection), eventCollection);
            
            
            // Create all new components
            ui = new UserInput(this);
            collisionDetector = new CollisionDetector(this);
            audio = new Audio(this);
            score = new PongScore(this);
            physics = new Physics(this);
            paddlePlayer1 = new PongPaddle(this, Resources.PADDLESIDE.LEFTPADDLE);
            paddlePlayer2 = new PongPaddle(this, Resources.PADDLESIDE.RIGHTPADDLE);
            ball = new PongBall(this);

            // Add instance functions to delegates
            ui.menuWasDisplayed += MenuChanged;
            ui.gameDescriptionSet += paddlePlayer1.GameDescriptionIsSet;
            ui.gameDescriptionSet += paddlePlayer2.GameDescriptionIsSet;

            // Add the UserInput component to the components collection
            Components.Add(ui);

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }
        
        /// <summary>
        /// PongGame is the Observer for the variable 'MenuDisplayed' in UserInput class
        /// Depending on whether the user is at the Menu Screen, MenuChanged(..) initializes the game
        /// and resets the game when the user presses 'escape'
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MenuChanged(object sender, MenuDisplayedEventArgs e) 
        {
            if (e.bMenuDisplayed == true && !Components.Contains(paddlePlayer1))    // Initialize the game by adding the rest of the game components
            {
                Components.Add(paddlePlayer1);
                Components.Add(paddlePlayer2);
                Components.Add(ball);
                Components.Add(audio);
                Components.Add(collisionDetector);
                Components.Add(physics);
                
                Components.Add(score);
            
            }
            else            // The menu displayed variable has been set to false, the game must be reset, and the menu will be displayed again
            {
                // Remove instance functions from delegates
                ui.gameDescriptionSet -= paddlePlayer1.GameDescriptionIsSet;
                ui.gameDescriptionSet -= paddlePlayer2.GameDescriptionIsSet;

                // Remove all components except UserInput
                Components.Remove(paddlePlayer1);
                Components.Remove(paddlePlayer2);
                Components.Remove(ball);
                Components.Remove(audio);
                Components.Remove(collisionDetector);
                Components.Remove(physics);
                
                Components.Remove(score);

                // Remove old event Collection
                Services.RemoveService(typeof(EventCollection));
                
                // Create new eventCollection and add to Services
                eventCollection = new EventCollection();
                Services.AddService(typeof(EventCollection), eventCollection);

                // Create all new components
                collisionDetector = new CollisionDetector(this);
                audio = new Audio(this);
                score = new PongScore(this);
                physics = new Physics(this);
                paddlePlayer1 = new PongPaddle(this, Resources.PADDLESIDE.LEFTPADDLE);
                paddlePlayer2 = new PongPaddle(this, Resources.PADDLESIDE.RIGHTPADDLE);
                ball = new PongBall(this);

                // Add new instance functions to delegates
                ui.gameDescriptionSet += paddlePlayer1.GameDescriptionIsSet;
                ui.gameDescriptionSet += paddlePlayer2.GameDescriptionIsSet;
                
            }

        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            spriteFont = Content.Load<SpriteFont>("pongFont1");
            Services.AddService(typeof(SpriteFont), spriteFont);
            //backgroundTexture2D = Content.Load<Texture2D>("grass");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (!IsFixedTimeStep && (fFrameRateAccumulator + (float)gameTime.ElapsedGameTime.TotalSeconds) < Resources.GAME_FRAME_RATE)   // If Fixed Time Step is off, then game needs to regulate its Game loop time
            {
                fFrameRateAccumulator += (float)gameTime.ElapsedGameTime.TotalSeconds;
                return;
            }
            else
            {
                fFrameRateAccumulator = 0;
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            //spriteBatch.Begin();
            //spriteBatch.Draw(backgroundTexture2D, Vector2.Zero, null, Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
            //spriteBatch.End();


            base.Draw(gameTime);
        }
    }
}
